package com.jingli.pile.util;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.function.BiFunction;
public class HMacMd5Utils extends AbstractHMacMd5Utils {
    static {
        try {
            Class.forName("com.jingli.pile.util.HMacMd5Provider");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成签名.
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @param charset 字符集.
     * @return 签名.
     * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
     */
    public static byte[] sign(String key, String operatorID, String data, String timestamp, String seq, Charset charset){
        return sign(key, assembleData(operatorID,data, timestamp, seq, charset));
    }


    /**
     * 生成签名Hex字符串.
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @param charset 字符集.
     * @return 签名.
     * <p>根据规范要求，字母大写。e.g.: 745166E8C43C84D37FFEC0F529C4136F</p>
     */
    public static String signHex(String key, String operatorID, String data, String timestamp, String seq, Charset charset){
        return HexUtils.bytes2HexString(sign(key, operatorID, data, timestamp, seq, charset)).toUpperCase();
    }

    /**
     * 生成签名.
     * <p>默认字符集:UTF-8</p>
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @return 签名.
     * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
     */
    public static byte[] sign(String key, String operatorID, String data, String timestamp, String seq){
        return sign(key, assembleData(operatorID,data, timestamp, seq, StandardCharsets.UTF_8));
    }

    /**
     * 生成签名Hex字符串.
     * <p>默认字符集:UTF-8</p>
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @return 签名.
     * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F</p>
     */
    public static String signHex(String key, String operatorID, String data, String timestamp, String seq){
        return HexUtils.bytes2HexString(sign(key, operatorID, data, timestamp, seq)).toUpperCase();
    }


    /**
     * 生成签名.
     * @param key 签名密钥.
     * @param data 数据.
     * @return 签名.
     * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
     */
    public static byte[] sign(String key, byte[] data){
        return withSign((fac, sign)->{
            try {
                KeySpec keySpec = HMacMd5KeySpec.of(key);
                PrivateKey pk = fac.generatePrivate(keySpec);

                sign.initSign(pk);
                sign.update(data);
                return sign.sign();
            } catch (InvalidKeyException|InvalidKeySpecException | SignatureException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 生成签名Hex字符串.
     * @param key 签名密钥.
     * @param data 数据.
     * @return 签名.
     * <p>e.g.: 745166E8C43C84D37FFEC0F529C4136F</p>
     */
    public static String signHex(String key, byte[] data){
        return HexUtils.bytes2HexString(sign(key, data)).toUpperCase();
    }

    /**
     * 验证Hex格式签名.
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param sign 签名.
     *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @return 验证结果.
     */
    public static boolean verifyHex(String key, String sign, String operatorID, String data, String timestamp, String seq){
        return verify(key, HexUtils.hexString2Bytes(sign), operatorID, data, timestamp, seq);
    }

    /**
     * 验证签名.
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param sign 签名.
     *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @return 验证结果.
     */
    public static boolean verify(String key, byte[] sign, String operatorID, String data, String timestamp, String seq){
        return verify(key, sign, assembleData(operatorID,data, timestamp, seq, StandardCharsets.UTF_8));
    }

    /**
     * 验证Hex格式签名.
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param sign 签名.
     *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @param charset 字符集.
     *
     * @return 验证结果.
     */
    public static boolean verifyHex(String key, String sign, String operatorID, String data, String timestamp, String seq, Charset charset){
        return verify(key, HexUtils.hexString2Bytes(sign), operatorID,data, timestamp, seq, charset);
    }

    /**
     * 验证签名.
     * @param key 签名密钥.
     *            <p>e.g.: 1234567890abcdef</p>
     * @param sign 签名.
     *             <p>e.g.:745166E8C43C84D37FFEC0F529C4136F(Hex格式)</p>
     * @param operatorID 运营商Id.
     *                   <p>e.g.: 123456789</p>
     * @param data 数据.
     *             <p>e.g.: il7B0BSEjFdzpyKzfOFpvg/Se1CP802RItKYFPfSLRxJ3jf0bVl9hvYOEktPAYW2nd7S8MBcyHYyacHKbISq5iTmDzG+ivnR+SZJv3USNTYVMz9rCQVSxd0cLlqsJauko79NnwQJbzDTyLooYoIwz75qBOH2/xOMirpeEqRJrF/EQjWekJmGk9RtboXePu2rka+Xm51syBPhiXJAq0GfbfaFu9tNqs/e2Vjja/ltE1M0lqvxfXQ6da6HrThsm5id4ClZFIi0acRfrsPLRixS/IQYtksxghvJwbqOsbIsITail9Ayy4tKcogeEZiOO+4Ed264NSKmk7l3wKwJLAFjCFogBx8GE3OBz4pqcAn/ydA=</p>
     * @param timestamp 时间戳.
     *                  <p>e.g.: 20160729142400</p>
     * @param seq 序列.
     *            <p>e.g.: 0001</p>
     * @param charset 字符集.
     * @return 验证结果.
     */
    public static boolean verify(String key, byte[] sign, String operatorID, String data, String timestamp, String seq, Charset charset){
        return verify(key, sign, assembleData(operatorID,data, timestamp, seq, charset));
    }


    /**
     * 验证数据包.
     * @param key 密钥.
     * @param signData 签名.
     * @param data 数据.
     * @return 验证是否成功.
     */
    public static boolean verify(String key, byte[] signData, byte[] data){
        return withSign((fac, sign)->{
            try {
                KeySpec keySpec = HMacMd5KeySpec.of(key);
                PublicKey pub = fac.generatePublic(keySpec);
                sign.initVerify(pub);
                sign.update(data);
                return sign.verify(signData);
            } catch (InvalidKeyException|InvalidKeySpecException | SignatureException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 验证数据包.
     * @param key 密钥.
     * @param signData 签名.
     * @param data 数据.
     * @return 验证是否成功.
     */
    public static boolean verifyHex(String key, String signData, byte[] data){
        return verify(key, HexUtils.hexString2Bytes(signData), data);
    }

    /**
     * 在密钥工厂和签名算法确定的条件下，执行回到函数.
     * @param callback 要执行的回调函数.
     * @param <T> 回调函数返回值类型.
     * @return 回调函数返回值.
     */
    private static <T> T withSign(BiFunction<KeyFactory, Signature, T> callback){
        try {
            KeyFactory fac = KeyFactory.getInstance("HMacMd5");
            Signature sign = Signature.getInstance("HMacMd5");

            return callback.apply(fac, sign);

        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

}
